iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 23
0

(本篇文章網誌版:http://shineright.blogspot.tw/2016/12/day-23.html)

昨天做完結束畫面後,這個遊戲大致完成了。

但遊戲進行時,背景一片藍藍的,實在很醜。所以今天就來把背景變得動態、好看一點。

先說一下我想做的背景長什麼樣子。我希望背景可以分為早上和晚上,早上背景呈淺藍色,並有一顆太陽從螢幕右側緩緩移向左側,晚上背景則呈深藍色,太陽則變成月亮。此外,早上會不斷有雲從上往下飄,讓玩家有主角在向上長高的感覺。晚上則把飄動的雲改成星星。

http://ithelp.ithome.com.tw/upload/images/20161223/20103149sJZNnTsGIl.png

http://ithelp.ithome.com.tw/upload/images/20161223/20103149LqGUEMAye2.png

在Scene中建立一個新的Sprite,命名為「Background」,並把它的Sprite指定為一個全白的矩形,這就是隨著早上和晚上改變顏色的背景了。把它的Transfrom Z變成正數(如5),使它在所有Game Object的最後方。

建立一個名為「BackgroundAnimation.cs」的C# Script。我一步一步來完成要呈現的動畫。先來做緩緩由淺藍變成深藍,再緩緩變回淺藍的背景。

public class BackgroundAnimation : MonoBehaviour 
{
	//白天或晚上的持續時間
    public float dayNightDuration;

	//白天時背景的顏色(淺藍色)
    public Color dayColor;

	//晚上時背景的顏色(深藍色)
    public Color nightColor;
    
	void Start()
    {
        StartCoroutine (MainCoroutine ());
    }
    
    
	//控制白天<->晚上的主要Coroutine
    IEnumerator MainCoroutine()
    {
        while (true) {
			//晚上,從nightColor變到dayColor
            StartCoroutine (ColorLerpCoroutine (nightColor, dayColor));
            
			//過了dayNightDuration時間,變成早上
            yield return new WaitForSeconds (dayNightDuration);
			
			//白天,從dayColor變到nightColor
            StartCoroutine (ColorLerpCoroutine (dayColor, nightColor));
            
			//過了dayNightDuration時間,變成晚上
            yield return new WaitForSeconds (dayNightDuration);
		}
    }
    
	//控制背景顏色的Coroutine
    IEnumerator ColorLerpCoroutine(Color fromColor, Color toColor)
    {
        float i = 0f;
        while (i <= 1f) {
			//計算上影格到這影格的時間佔dayNightDuration多少時間,並加至i上
            i += Time.deltaTime / dayNightDuration;

			//設定SpriteRenderer的color
            GetComponent<SpriteRenderer> ().color = Color.Lerp (fromColor, toColor, i);

			yield return null;
        }
    }
}

這段程式碼有許多複雜的地方。首先來講public變數,dayNightDuration代表白天和晚上的持續時間,也就是由白天轉換到晚上、晚上轉換到白天所需的時間。同為Color型態的dayColornightColor則可讓我在Inspector中選則白天和晚上背景的顏色。

Start()中,簡單地使MainCoroutine()開始。

MainCoroutine()是這段程式碼中最主要的Coroutine,用以控制白天和晚上的變化。我讓遊戲開始於晚上,並使ColorLerpCoroutine(nightColor, dayColor)開始,讓背景的顏色由nightColor緩緩轉變為dayColor。呼叫ColorLerpCoroutine()緩緩更變顏色時,MainCoroutine()yield return new WaitForSeconds(dayNightDuration)等待顏色更變完成,邁入早上。

最後,來看ColorLerpCoroutine()。這其實是一個Unity Scripting中非常典型的Coroutine。它會在一段時間內緩緩改變Game Object的某一屬性。我先設float i=0f,進入while後,i會不斷隨時間增加,增加的量為「過去的時間」和「更變屬性動畫的總時間」的比例,也就是Time.deltaTime / dayNightDuration。如此,在一次次的while中,i會不斷隨時間由0增加到1。但這個i究竟是要幹嘛的呢?其實是為了底下呼叫線性插值法(Linear Interpolation,也就是Lerp)用的。Color.Lerp(fromColor, toColor, i)是使用插值法(fromColor + i x (toColor - fromColor))回傳一個fromColortoColor之間的數。更改完顏色,以yield return null把控制交還給Unity,等待下一個影格再次運行while()

回到Unity Editor,為背景Sprite加上Background Animation (Script) Component,設定好早上晚上的持續時間與顏色,進入Play Mode,背景顏色就會慢慢變換了。

接著來做移動的月亮與太陽。為BackgroundAnimation加上幾行public變數:

public Transform sunMoonStartTransform; //太陽/月亮起始位置
public Transform sunMoonEndTransform;  //太陽/月亮最終位置
public GameObject sun;  //太陽
public GameObject moon;  //月亮

再多寫一個Coroutine:

IEnumerator SunMoonMovingCoroutine(GameObject obj)
{
    float i = 0f;
    while (i <= 1f) {
        i += Time.deltaTime / dayNightDuration;
        obj.transform.position = Vector3.Lerp (sunMoonStartTransform.transform.position, sunMoonEndTransform.transform.position, i);
        yield return null;
    }
}

這個Coroutine和改變背景顏色的Coroutine很像,只是把Color換成Vector3。這會讓月亮或太陽隨時間從sunMoonStartTransform的位置移動到sunMoonEndTransform的位置。

寫完新的Coroutine就可以來為MainCoroutine加料了。在MainCoroutine//晚上//白天 處各加上

StartCoroutine (SunMoonMovingCoroutine (moon));

StartCoroutine (SunMoonMovingCoroutine (sun));

回到Unity Editor。新增兩個Game Object,分別代表太陽/月亮的起點和終點,並增加太陽和月亮的Game Object到Scene上,再把它們拉至Background Animation (Script) Component對應的欄位。進入Play Mode,現在不只背景顏色變化,日月也會交替了。

最後,來做下降的星星和雲吧!把不同形狀的星星和雲的Sprite都拉到Scene上,為它們增加Random Initial Speed (Script)、Destroy After Seconds (Script),並設定好數值(Destroy After Seconds應設為從螢幕最上方到螢幕最下方所需的時間)。把它們都拉到Project欄成為Prefab,就可以再度開啟BackgroundAnimation.cs,為背景動畫加工了。

在BackgroundAnimation.cs再新增幾個public變數。

public Transform cloudStarMinSpawnTransform; //星星或雲生成的最小(左)位置
public Transform cloudStarMaxSpawnTransform; //星星或雲生成的最大(右)位置
public GameObject[] cloudPrefabs; //所有雲的Prefab
public GameObject[] starPrefabs; //所有星星的Prefab
public float cloudStarSpawnMinInterval;  //一波星星或雲的最小間隔時間
public float cloudStarSpawnMaxInterval;  //一波星星或雲的最大間隔時間
public int cloudStarSpawnMinAmount; //一波星星或雲的最小個數
public int cloudStarSpawnMaxAmount; //一波星星或雲的最大個數

接著,再新增一個Coroutine:

IEnumerator SpawnCloudStarCoroutine(bool cloud)
{
    while (true) {
		//要產生星星或雲的數量
        int spawnAmount = Random.Range (cloudStarSpawnMinAmount, cloudStarSpawnMaxAmount + 1);
        
        while (spawnAmount > 0) {
			//產生
            Instantiate(
                cloud ? cloudPrefabs[Random.Range(0, cloudPrefabs.Length)] : starPrefabs[Random.Range(0, starPrefabs.Length)],
                new Vector3(
                    Random.Range(cloudStarMinSpawnTransform.transform.position.x, cloudStarMaxSpawnTransform.transform.position.x),
                    cloudStarMinSpawnTransform.transform.position.y,
                    cloudStarMinSpawnTransform.transform.position.z
                ),
                Quaternion.identity
            );
            
            spawnAmount--;
        }
        
		//等待下一波的時間
        yield return new WaitForSeconds (Random.Range (cloudStarSpawnMinInterval, cloudStarSpawnMaxInterval));
    }
}

這個Coroutine相當簡單,每隔一段時間產生一波星星或雲,而每波星星或雲的數量都不一樣(在[cloudStarSpawnMinAmount, cloudStarSpawnMaxAmount]之間)。每個產生的星星或雲都在cloudStarMaxSpawnTransformcloudStarMinSpawnTransform之間。因為這兩個座標只有X軸相異,所以也只有X軸需呼叫Random.Range()產生隨機數值。

完成新的Coroutine後,馬上來修變MainCoroutine()

IEnumerator MainCoroutine()
{
    //儲存上一個生成星星或雲的Coroutine
    Coroutine spawnCoroutine = null;
       
    while (true) {
	    //若上一個生成雲的Coroutine存在,則停止它
        if (spawnCoroutine != null)
            StopCoroutine (spawnCoroutine);
                
		//呼叫生成星星Coroutine
        spawnCoroutine = StartCoroutine (SpawnCloudStarCoroutine (false));

		//—-↓舊的程式碼↓—-//
        StartCoroutine (ColorLerpCoroutine (nightColor, dayColor));
        StartCoroutine (SunMoonMovingCoroutine (moon));
        yield return new WaitForSeconds (dayNightDuration);
        //—-↑舊的程式碼↑—-//
			
		//停止生成星星的Coroutine
        StopCoroutine (spawnCoroutine);

		//呼叫生成雲的Coroutine
        spawnCoroutine = StartCoroutine (SpawnCloudStarCoroutine (true));

		//—-↓舊的程式碼↓—-//
        StartCoroutine (ColorLerpCoroutine (dayColor, nightColor));
        StartCoroutine (SunMoonMovingCoroutine (sun));
        yield return new WaitForSeconds (dayNightDuration);
		//—-↑舊的程式碼↑—-//
    }
}

沒錯,Coroutine可以像變數一樣被傳來傳數。因為在呼叫下一個SpawnCloudStarCoroutine之前,必須先把前一個停止(生成雲之前需把生成星星的Coroutine停掉,反之亦然)。所以必須另宣告一個Coroutine形態的變數來儲存上一個Coroutine,之後才能以StopCoroutine()停止它。此外,一開始因為spawnCoroutinenull,所以必須多一個if式來檢查,不然會停止到不存在的Coroutine。

回到Unity Editor,再新增兩個代表星星和雲生成範圍的Game Object,拖到Cloud Star Min/Max Spawn欄,並設定好它們生成的數值。進入Play Mode,星星和雲出現啦!

此外,要讓背景的雲和星星更多樣化,可以為它們的Prefab再加上下面兩段Script來隨機它們的旋轉角度和大小比例。

//隨機大小比例
public class RandomInitialLocalScale : MonoBehaviour 
{
    public float minVal;
    public float maxVal;
    
    void Start()
    {
        transform.localScale = transform.localScale * Random.Range (minVal, maxVal);
    }
}
//隨機旋轉角度
public class RandomInitialRotation : MonoBehaviour 
{
    void Start()
    {
        transform.rotation = Quaternion.Euler (0f, 0f, Random.Range (0f, 359f));
    }
}

http://ithelp.ithome.com.tw/upload/images/20161223/20103149DoloxX6RHU.png

待續。


上一篇
Day 22: 暫停、遊戲結束畫面
下一篇
Day 24: 音效與背景音樂
系列文
我要和天一樣高!!!(Unity 2D手機小遊戲開發日誌)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言